/**
 * @file .
 * @author wangxiaowei <wangxiaowei0303@163.com>
 * @date 2015-05-28 19:36
 * 目录遍历模块,提供了目录遍历的功能.
 */

#ifndef org_wxw0303_bishe_src_DirTraverse_h_
#define org_wxw0303_bishe_src_DirTraverse_h_


#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <string.h>
#include "exception_util.h"

/*
 * DirTraverse,其对象提供了目录遍历的能力.
 * 具有属性:
 *   recursive; 控制遍历对象是否遍历目录下的子目录.默认会遍历.
 * 如何使用;
 *   例子;
 *     DirTraverse dirTraverse;
 *     dirTraverse.setRecursive(false);
 *     dirTraverse.traverse("/home",遍历函数);
 */
class DirTraverse {
public:
    // inline DirTraverse();
    // inline ~DirTraverse();
    
    inline void
    setRecursive(bool r);
    
    inline bool
    recursive() const;
    
    /*
     * 使用深度遍历遍历以 dir 为根的目录树,对于遍历过程获取到的每一个 dir entry
     * 都调用 func() 方法.并且会忽略 func() 抛出的任何异常.
     */
    template<typename T>
    void
    traverse(const char *dir,T &func) const;

private:
    bool recursive_;
};

inline void
DirTraverse::setRecursive(bool r)
{
    this->recursive_ = r;
}

inline bool
DirTraverse::recursive() const
{
    return this->recursive_;
}

template<typename T>
void
DirTraverse::traverse(const char *dir,T &func) const
{
    /* 保存当前工作目录,以便在函数返回时恢复. */
    int cwdfd = open(".",O_RDONLY);
    if (cwdfd < 0) 
        THROW("open(\".\",O_RDONLY): %m\n");
    
    DIR *dirstream = opendir(dir);
    if (dirstream == 0) {
        close(cwdfd);
        THROW("opendir(%s): %m\n",dir);
    }

    if (fchdir(dirfd(dirstream)) != 0) {
        closedir(dirstream);
        close(cwdfd);
        THROW("chdir(%s): %m\n",dir);
    }

    struct dirent entry;
    struct dirent *result = 0;
    int ret = 0;
    while ((ret = readdir_r(dirstream,&entry,&result)) == 0) {
        if (result == 0) 
            break;
        try {
            func(&entry);
        } catch (...) {
            ;
        }
        if (this->recursive_ == true && entry.d_type == DT_DIR && 
            strcmp(entry.d_name,".") != 0 && strcmp(entry.d_name,"..") != 0) 
        {
            try {
                this->traverse(entry.d_name,func);
            } catch (...) {
                ret = fchdir(cwdfd);
                closedir(dirstream);
                close(cwdfd);
                throw;
            }
        }
    }
    
    /* 
     * 编译器总是给我说,你忽略了 fchdir() 返回值,那是我故意的好么,就算我不
     * 忽略(就像下面那样),但是如果 fchdir() 真出错了,我又能干啥例?
     */
    int tmpret = fchdir(cwdfd);
    if (tmpret != 0) {
        /* 
         * Oh,NO,fchdir() 失败了,我们无法切换到原来的工作目录了!
         * 等等,我得做点什么,做点什么,但是我能做啥呢!!!
         * 为什么我要加上这个分支,因为编译器"提示"我,我未使用过 tmpret.
         */
    }
    closedir(dirstream);
    close(cwdfd);
    if (ret != 0)
        THROW("readdir_r(): %s",strerror(ret));
    return ;
}



#endif /* org_wxw0303_bishe_src_DirTraverse_h_ */
